/*
   Copyright (C) 2005, 2006, 2008 MySQL AB, 2009 Sun Microsystems, Inc.
    All rights reserved. Use is subject to license terms.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

#ifndef NdbIndexStat_H
#define NdbIndexStat_H

#include <ndb_types.h>
#include "NdbDictionary.hpp"
#include "NdbError.hpp"
#include "NdbIndexScanOperation.hpp"
class NdbIndexStatImpl;

/*
 * Ordered index stats "v4".  Includes 1) the old records_in_range in
 * simplified form 2) the new scanned and stored stats.  These are
 * completely different.  1) makes a one-round-trip query directly to
 * the index while 2) reads more extensive stats from sys tables where
 * they were stored previously by NDB kernel.
 *
 * Methods in general return 0 on success and -1 on error.  The error
 * details are available via getNdbError().
 */

class NdbIndexStat {
public:
  NdbIndexStat();
  ~NdbIndexStat();

  // dummy defs to make handler compile at "ndb api" patch level
  int alloc_cache(Uint32 entries) { return 0; }
  enum { RR_UseDb = 1, RR_NoUpdate = 2 };

  /*
   * Get latest error.  Can be printed like any NdbError instance and
   * includes some extras.
   */
  struct Error : public NdbError {
    int line;  // source code line number
    int extra; // extra error code
    Error();
  };
  const Error& getNdbError() const;

  /*
   * Estimate how many records exist in given range.  Does a single
   * tree-dive on each index fragment, estimates the count from tree
   * properties, and sums up the results.
   *
   * Caller provides index and scan transaction and range bounds.
   * A scan operation is created and executed.  The result is returned
   * in out-parameter "count".  The result is not transactional.  Value
   * zero is exact (range was empty when checked).
   *
   * This is basically a static method.  The class instance is used only
   * to return errors.
   */
  int records_in_range(const NdbDictionary::Index* index,
                       NdbTransaction* trans,
                       const NdbRecord* key_record,
                       const NdbRecord* result_record,
                       const NdbIndexScanOperation::IndexBound* ib,
                       Uint64 table_rows, // not used
                       Uint64* count,
                       int flags); // not used

  /*
   * Methods for stored stats.
   *
   * There are two distinct users: 1) writer reads samples from sys
   * tables and creates a new query cache 2) readers make concurrent
   * stats queries on current query cache.
   *
   * Writer provides any Ndb object required.  Its database name must be
   * "mysql".  No reference to it is kept.
   *
   * Readers provide structs such as Bound on stack or in TLS.  The
   * structs are opaque.  With source code the structs can be cast to
   * NdbIndexStatImpl structs.
   */

  enum {
    NoSysTables = 4714,   // all sys tables missing
    NoIndexStats = 4715,  // given index has no stored stats
    UsageError = 4716,    // wrong state, invalid input
    NoMemError = 4717,
    InvalidCache = 4718,
    InternalError = 4719,
    BadSysTables = 4720,  // sys tables partly missing or invalid
    HaveSysTables = 4244, // create error if all sys tables exist
    NoSysEvents = 4710,
    BadSysEvents = BadSysTables,
    HaveSysEvents = 746
  };

  /*
   * Methods for sys tables.
   *
   * Create fails if any objects exist.  Specific errors are
   * BadSysTables (drop required) and HaveSysTables.
   *
   * Drop always succeeds and drops any objects that exist.
   *
   * Check succeeds if all correct objects exist.  Specific errors are
   * BadSysTables (drop required) and NoSysTables.
   *
   * Database of the Ndb object is used and must be "mysql" for kernel
   * to see the tables.
   */
  int create_systables(Ndb* ndb);
  int drop_systables(Ndb* ndb);
  int check_systables(Ndb* ndb);

  /*
   * Set index operated on.  Allocates internal structs.  Makes no
   * database access and keeps no references to the objects.
   */
  int set_index(const NdbDictionary::Index& index,
                const NdbDictionary::Table& table);

  /*
   * Release index.  Required only if re-used for another index.
   */
  void reset_index();

  /*
   * Trivial invocation of NdbDictionary::Dictionary::updateIndexStat.
   */
  int update_stat(Ndb* ndb);

  /*
   * Trivial invocation of NdbDictionary::Dictionary::deleteIndexStat.
   */
  int delete_stat(Ndb* ndb);

  /*
   * Cache types.
   */
  enum CacheType {
    CacheBuild = 1,     // new cache under construction
    CacheQuery = 2,     // cache used to answer queries
    CacheClean = 3      // old caches waiting to be deleted
  };

  /*
   * Move CacheQuery (if any) to CacheClean and CacheBuild (if any) to
   * CacheQuery.  The CacheQuery switch is atomic.
   */
  void move_cache();

  /*
   * Delete all CacheClean instances.  This can be safely done after old
   * cache queries have finished.  Cache queries are fast since they do
   * binary searches in memory.
   */
  void clean_cache();

  /*
   * Cache info.  CacheClean may have several instances and the values
   * for them are summed up.
   */
  struct CacheInfo {
    Uint32 m_count;       // number of instances
    Uint32 m_valid;       // should be except for incomplete CacheBuild
    Uint32 m_sampleCount; // number of samples
    Uint32 m_totalBytes;  // total bytes memory used
    Uint64 m_save_time;   // microseconds to read stats into cache
    Uint64 m_sort_time;   // microseconds to sort the cache
    // end v4 fields
  };

  /*
   * Get info about a cache type.
   */
  void get_cache_info(CacheInfo& info, CacheType type) const;

  /*
   * Saved head record retrieved with get_head().  The database fields
   * are updated by any method which reads stats tables.  Stats exist if
   * sampleVersion is not zero.
   */
  struct Head {
    Int32 m_found;        // -1 no read done, 0 = no record, 1 = exists
    Int32 m_eventType;    // if polling, NdbDictionary::Event::TE_INSERT etc
    Uint32 m_indexId;
    Uint32 m_indexVersion;
    Uint32 m_tableId;
    Uint32 m_fragCount;
    Uint32 m_valueFormat;
    Uint32 m_sampleVersion;
    Uint32 m_loadTime;
    Uint32 m_sampleCount;
    Uint32 m_keyBytes;
    // end v4 fields
  };

  /*
   * Get latest saved head record.  Makes no database access.
   */
  void get_head(Head& head) const;

  /*
   * Read stats head record for the index.  Returns error and sets code
   * to NoIndexStats if head record does not exist or sample version is
   * zero.  Use get_head() to retrieve the results.
   */
  int read_head(Ndb* ndb);

  /*
   * Read current version of stats into CacheBuild.  A move_cache() is
   * required before it is available for queries.
   */
  int read_stat(Ndb* ndb);

  /*
   * Reader provides bounds for cache query.  The struct must be
   * initialized from a thread-local byte buffer of the given size.
   * NdbIndexStat instance is used and must have index set.  Note that
   * a bound becomes low or high only as part of Range.
   */
  enum { BoundBufferBytes = 8192 };
  struct Bound {
    Bound(const NdbIndexStat* is, void* buffer);
    void* m_impl;
  };

  /*
   * Add non-NULL attribute value to the bound.  May return error for
   * invalid data.
   */
  int add_bound(Bound& bound, const void* value);

  /*
   * Add NULL attribute value to the bound.
   */
  int add_bound_null(Bound& bound);

  /*
   * A non-empty bound must be set strict (true) or non-strict (false).
   * For empty bound this must remain unset (-1).
   */
  void set_bound_strict(Bound& bound, int strict);

  /*
   * To re-use same bound instance, a reset is required.
   */
  void reset_bound(Bound& bound);

  /*
   * Queries take a range consisting of low and high bound (start key
   * and end key in mysql).
   */
  struct Range {
    Range(Bound& bound1, Bound& bound2);
    Bound& m_bound1;
    Bound& m_bound2;
  };

  /*
   * After defining bounds, the range must be finalized.  This updates
   * internal info.  Usage error is possible.
   */
  int finalize_range(Range& range);

  /*
   * Reset the bounds.
   */
  void reset_range(Range& range);

  /*
   * Convert NdbRecord index bound to Range.  Invokes reset and finalize
   * and cannot be mixed with the other methods.
   */
  int convert_range(Range& range,
                    const NdbRecord* key_record,
                    const NdbIndexScanOperation::IndexBound* ib);

  /*
   * Reader provides storage for stats values.  The struct must be
   * initialized from a thread-local byte buffer of the given size.
   */
  enum { StatBufferBytes = 2048 };
  struct Stat {
    Stat(void* buffer);
    void* m_impl;
  };

  /*
   * Compute Stat for a Range from the query cache.  Returns error
   * if there is no valid query cache.  The Stat is used to get
   * stats values without further reference to the Range.
   */
  int query_stat(const Range& range, Stat& stat);

  /*
   * Check if range is empty i.e. bound1 >= bound2 (for bounds this
   * means empty) or the query cache is empty.  The RIR and RPK return
   * 1.0 if range is empty.
   */
  static void get_empty(const Stat& stat, bool* empty);

  /*
   * Get estimated RIR (records in range).  Value is always >= 1.0 since
   * no exact 0 rows can be returned.
   */
  static void get_rir(const Stat& stat, double* rir);

  /*
   * Get estimated RPK (records per key) at given level k (from 0 to
   * NK-1 where NK = number of index keys).  Value is >= 1.0.
   */
  static void get_rpk(const Stat& stat, Uint32 k, double* rpk);

  /*
   * Get a short string summarizing the rules used.
   */
  enum { RuleBufferBytes = 80 };
  static void get_rule(const Stat& stat, char* buffer);

  /*
   * Events (there is 1) for polling.  These are dictionary objects.
   * Correct sys tables must exist.  Drop ignores non-existing events.
   */
  int create_sysevents(Ndb* ndb);
  int drop_sysevents(Ndb* ndb);
  int check_sysevents(Ndb* ndb);

  /*
   * Create listener for stats updates.  Only 1 is allowed.
   */
  int create_listener(Ndb* ndb);

  /*
   * Start listening for events (call NdbEventOperation::execute).
   */
  int execute_listener(Ndb* ndb);

  /*
   * Poll the listener (call Ndb::pollEvents).  Returns 1 if there are
   * events available and 0 otherwise, or -1 on failure as usual.
   */
  int poll_listener(Ndb* ndb, int max_wait_ms);

  /*
   * Get next available event.  Returns 1 if a new event was returned
   * and 0 otherwise, or -1 on failure as usual.  Use get_heed() to
   * retrieve event type and data.
   */
  int next_listener(Ndb* ndb);

  /*
   * Drop the listener.
   */
  int drop_listener(Ndb* ndb);

  /*
   * Memory allocator for stats cache data (key and value byte arrays).
   * Implementation default uses malloc/free.  The memory in use is the
   * sum of CacheInfo::m_totalBytes from all cache types.
   */
  struct Mem {
    Mem();
    virtual ~Mem();
    virtual void* mem_alloc(UintPtr size) = 0;
    virtual void mem_free(void* ptr) = 0;
  };

  /*
   * Set a non-default memory allocator.
   */
  void set_mem_handler(Mem* mem);

  // get impl class for use in NDB API programs
  NdbIndexStatImpl& getImpl();

private:
  int addKeyPartInfo(const NdbRecord* record,
                     const char* keyRecordData,
                     Uint32 keyPartNum,
                     const NdbIndexScanOperation::BoundType boundType,
                     Uint32* keyStatData,
                     Uint32& keyLength);

  // stored stats

  friend class NdbIndexStatImpl;
  NdbIndexStat(NdbIndexStatImpl& impl);
  NdbIndexStatImpl& m_impl;
};

class NdbOut&
operator<<(class NdbOut& out, const NdbIndexStat::Error&);

#endif
